﻿using System;
using System.IO.IsolatedStorage;
using System.ServiceModel;
using System.Windows;

using GroupChallengeWP7.GroupChallengeSvc;

namespace GroupChallengeWP7.Models
{
    public class Network
    {
        private const string HostServerKey = "HostServer";
        private const string DefaultHostServer = "LocalHost"; // "DallasXaml.com"

        private GameState _gameState;

        private string _hostServer = DefaultHostServer;
        private DateTime _lastCheckTime;
        private DateTime _lastChangeTime;
        private string _lastConnectionStatus = null;

        private GroupChallengeClient _client = null;

        public event Action NetworkStatusChanged;

        public Network(GameState gameState)
        {
            _gameState = gameState;
        }

        public string HostServer
        {
            get { return _hostServer; }
            set
            {
                _hostServer = value;

                // Set back to a default localhost.
                if (string.IsNullOrEmpty(_hostServer))
                    _hostServer = DefaultHostServer;

                // The server may have changed - clear for next request.
                _client = null;
            }
        }

        public DateTime LastCheckTime
        {
            get { return _lastCheckTime; }
        }

        public DateTime LastChangeTime
        {
            get { return _lastChangeTime; }
        }

        public string LastConnectionStatus
        {
            get { return _lastConnectionStatus; }
        }

        // Save settings to isolated storage.
        public void SaveSettings(IsolatedStorageSettings settings)
        {
            // Add the setting if not already.
            if (!settings.Contains(HostServerKey))
                settings.Add(HostServerKey, DefaultHostServer);

            settings[HostServerKey] = _hostServer;
        }

        // Load previous settings from isolated storage.
        public void LoadSettings(IsolatedStorageSettings settings)
        {
            settings.TryGetValue<string>(HostServerKey, out _hostServer);

            if (string.IsNullOrEmpty(_hostServer))
                _hostServer = DefaultHostServer;
        }

        // Client instance to the web service.  Can be recreated to different server any time so we are careful to unregister events throughout.
        private GroupChallengeClient Client
        {
            get
            {
                if (null != _client)
                {
                    if (_client.State != System.ServiceModel.CommunicationState.Created &&
                        _client.State != System.ServiceModel.CommunicationState.Opened)
                    {
                        // Unsubscribe the event in case the server is changed - don't stack more.
                        _client.GetCurrentGameStateCompleted -= new EventHandler<GroupChallengeSvc.GetCurrentGameStateCompletedEventArgs>(CompletedGetCurrentGameState);

                        _client.Abort();
                        _client = null;
                    }
                }

                if (null == _client)
                {
                    string endpointConfigurationName = "BasicHttpBinding_IGroupChallenge";
                    string endpointUrl = string.Format("http://{0}/GroupChallenge/GroupChallenge.svc", HostServer);
                    EndpointAddress endpointAddress = new EndpointAddress(endpointUrl);

                    _client = new GroupChallengeSvc.GroupChallengeClient(endpointConfigurationName, endpointAddress);

                    // Setup the game polling callback once.
                    _client.GetCurrentGameStateCompleted += new EventHandler<GroupChallengeSvc.GetCurrentGameStateCompletedEventArgs>(CompletedGetCurrentGameState);
                }

                return _client;
            }
        }

        public void JoinGame(Player player)
        {
            Client.JoinGameCompleted += new EventHandler<JoinGameCompletedEventArgs>(CompletedJoinGame);
            Client.JoinGameAsync(player.NickName, player.FullName, player.PlayerId, player.NickName);
        }

        private void CompletedJoinGame(object sender, JoinGameCompletedEventArgs e)
        {
            _lastConnectionStatus = HandleJoinGameCompleted(e);
            FireNetworkStatusChanged();

            // Unregister from the client.
            Client.JoinGameCompleted -= new EventHandler<GroupChallengeSvc.JoinGameCompletedEventArgs>(CompletedJoinGame);
        }

        private string HandleJoinGameCompleted(JoinGameCompletedEventArgs e)
        {
            if (null != e.Error)
            {
                return "Unable to join the game.\r\n\r\nReason: " + e.Error.Message;
            }
            else
            {
                // Join Result: 0-Unknown, 1-Success, 2-Validation Error, 3-Nickname already used.
                FriendlyResult friendlyResult = e.Result as FriendlyResult;
                string newNickName = e.UserState as string;

                if (string.IsNullOrEmpty(newNickName))
                {
                    return "Missing nick name when joining the game.";
                }
                else if (friendlyResult == null)
                {
                    return "Invalid return when joining the game.";
                }
                else if (friendlyResult.ResultId == 1)  // Success
                {
                    Deployment.Current.Dispatcher.BeginInvoke(() => _gameState.JoinGame());
                    return null;
                }
                else                                    // Other error from server validation.
                {
                    // 0-Unknown, 1-Success, 2-Validation Error, 3-Nickname already used, 4-Player Id Not Valid
                    return friendlyResult.FriendlyText;
                }
            }
        }

        public void LeaveGame(Player player)
        {
            Client.LeaveGameCompleted += new EventHandler<GroupChallengeSvc.LeaveGameCompletedEventArgs>(CompletedLeaveGame);
            Client.LeaveGameAsync(player.NickName);
        }

        private void CompletedLeaveGame(object sender, GroupChallengeSvc.LeaveGameCompletedEventArgs e)
        {
            _lastConnectionStatus = HandleLeaveGameCompleted(e);
            FireNetworkStatusChanged();

            // Unregister from the client.
            Client.LeaveGameCompleted -= new EventHandler<GroupChallengeSvc.LeaveGameCompletedEventArgs>(CompletedLeaveGame);
        }

        private string HandleLeaveGameCompleted(GroupChallengeSvc.LeaveGameCompletedEventArgs e)
        {
            if (null != e.Error)
            {
                return "Unable to properly leave the game.\r\n\r\nReason: " + e.Error.Message;
            }
            else
            {
                if (e.Result)
                {
                    Deployment.Current.Dispatcher.BeginInvoke(() => _gameState.LeaveGame());
                    return null;
                }
                else
                {
                    return "You were not in the current game.";
                }
            }
        }

        public void Answer(int answerNumber, string nickName, Question currentQuestion)
        {
            Client.AnswerQuestionCompleted += new EventHandler<GroupChallengeSvc.AnswerQuestionCompletedEventArgs>(CompletedAnswerQuestion);
            Client.AnswerQuestionAsync(nickName, currentQuestion.QuestionId, answerNumber);
        }

        private void CompletedAnswerQuestion(object sender, GroupChallengeSvc.AnswerQuestionCompletedEventArgs e)
        {
            _lastConnectionStatus = HandleAnswerQuestionCompleted(e);
            FireNetworkStatusChanged();

            // Unregister from the client - we don't know if the client is new - register and unregister each time.
            Client.AnswerQuestionCompleted -= new EventHandler<GroupChallengeSvc.AnswerQuestionCompletedEventArgs>(CompletedAnswerQuestion);
        }

        private string HandleAnswerQuestionCompleted(GroupChallengeSvc.AnswerQuestionCompletedEventArgs e)
        {
            if (null != e.Error)
            {
                return "Unable to properly answer the question.\r\n\r\nReason: " + e.Error.Message;
            }
            else
            {
                if (e.isFirstToAnswer)
                {
                    Deployment.Current.Dispatcher.BeginInvoke(() => _gameState.ScorePoint());
                }
                return null;
            }
        }

        public void GetCurrentGameState()
        {
            _lastCheckTime = DateTime.Now;

            Client.GetCurrentGameStateAsync(_lastChangeTime);
        }

        // Callback from GetCurrentGameStateAsync to update game state.
        private void CompletedGetCurrentGameState(object sender, GroupChallengeSvc.GetCurrentGameStateCompletedEventArgs e)
        {
            _lastConnectionStatus = HandleGetCurrentGameStateCompleted(e);
            FireNetworkStatusChanged();
        }

        private string HandleGetCurrentGameStateCompleted(GroupChallengeSvc.GetCurrentGameStateCompletedEventArgs e)
        {
            try
            {
                if (null != e.Error)
                {
                    // Repetitive message when server is unavailable.
                    return "Error";
                }
                else
                {
                    // Change occurred.  Set the game and question.
                    if (_lastChangeTime != e.lastChangeTime)
                    {
                        Deployment.Current.Dispatcher.BeginInvoke(() => _gameState.UpdateGameState(e.currentGame, e.currentQuestion));
                        _lastChangeTime = e.lastChangeTime;
                    }
                    return null;
                }
            }
            catch (EndpointNotFoundException)
            {
                return "Host Not Available";
            }
            catch (CommunicationException)
            {
                return "Host Communication Error";
            }
            catch (Exception ex)
            {
                return "Error: " + ex.Message;
            }
        }

        private void FireNetworkStatusChanged()
        {
            Deployment.Current.Dispatcher.BeginInvoke(delegate
            {
                if (NetworkStatusChanged != null)
                    NetworkStatusChanged();
            });
        }
    }
}
